package com.olap.starter.config;


import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;

import java.lang.reflect.Field;
import java.sql.SQLException;

/**
 * @author WBG
 * @date 2022/9/5 9:07
 * @describe
 */

//要在分页插件之前完成sql语句的修改 应拦截Executor
@Intercepts(
        {
                @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
                        Object.class}),
        }
)
@Slf4j
public class VersionInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取拦截下的mapper
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        if (mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
            //获取上一个拦截器传过来的sql
            BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
            String oldSql = boundSql.getSql(); //获取到当前需要被执行的SQL
            VersionPlus version = getVersion(mappedStatement);
            if(version != null){
                String newSql = getNewSql(oldSql, version.value());
                if(newSql != null){
                    log.info("系统自动更新版本号");
                    resetSql2Invocation(invocation,newSql);
                }
            }
        }
        return invocation.proceed();
    }


    /**
     * 包装sql后，重置到invocation中
     *
     * @param invocation
     * @param sql
     * @throws SQLException
     */
    public static Invocation resetSql2Invocation(Invocation invocation, String sql) throws SQLException {
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        msObject.setValue("sqlSource.boundSql.sql", sql);
        args[0] = newStatement;
        return invocation;
    }

    private static MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder =
                new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

    private VersionPlus getVersion(MappedStatement mappedStatement) {
        ParameterMap parameterMap = mappedStatement.getParameterMap();
        Class<?> type = parameterMap.getType();
        Field[] fields = type.getDeclaredFields();
        for (Field field : fields) {
            VersionPlus annotation = field.getAnnotation(VersionPlus.class);
            if (annotation != null) {
                return annotation;
            }


        }
        return null;
    }

    private static String getNewSql(String oldSql, int v) {
        int version = oldSql.indexOf("version");
        if (version != -1) {
            log.info("sql已存在版本号更新，不需要自动更新；");
            //return oldSql.replace("version=?", "version=version+1");  如果sql存在  使用sql的
            return null;
        } else {
            StringBuilder newSql = new StringBuilder();
            int where = oldSql.indexOf("WHERE");
            newSql.append(oldSql.substring(0, where).replaceAll("\\n", "").trim()).append(",version=version+").append(v).append(" ").append(oldSql.substring(where));
            return newSql.toString();
        }
    }

    //    定义一个内部辅助类，作用是包装sq
    static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}
